import pandas as pd
import yaml
import logging
import re
import numpy as np


class CustomCSVData:
    @classmethod
    def load_data(cls, file_path, dtformat='%Y/%m/%d'):
        """
        加载CSV数据并处理各种数据类型问题

        参数:
            file_path: CSV文件路径
            dtformat: 日期格式字符串

        返回:
            pandas.DataFrame: 处理后的数据框
        """
        # 创建日志记录器
        logger = logging.getLogger(__name__)
        logger.info(f"加载数据文件: {file_path}")

        try:
            # 读取CSV文件
            df = pd.read_csv(file_path)
            logger.info(f"成功读取数据，共 {len(df)} 行")
            logger.debug(f"原始数据列: {df.columns.tolist()}")
            logger.debug(f"原始数据前5行:\n{df.head()}")

            # 重命名列以匹配标准格式 - 更健壮的实现
            if 'datatime' in df.columns:
                df = df.rename(columns={'datatime': 'datetime'})
                logger.info(f"重命名列 'datatime' -> 'datetime'")
            elif 'datetime' not in df.columns:
                logger.error("CSV文件中未找到日期列（datetime或datatime）")
                raise ValueError("CSV文件中未找到日期列")

        except Exception as e:
            logger.error(f"读取CSV文件失败: {str(e)}")
            raise

        # 检查必需的列
        required_columns = ['datetime', 'close', 'open', 'high', 'low']
        missing_columns = [col for col in required_columns if col not in df.columns]

        if missing_columns:
            logger.error(f"CSV文件缺少必需的列: {', '.join(missing_columns)}")
            raise ValueError(f"CSV文件缺少必需的列: {', '.join(missing_columns)}")

        try:
            # 转换日期列
            initial_count = len(df)
            df['datetime'] = pd.to_datetime(df['datetime'], format=dtformat, errors='coerce')

            # 删除无效日期行
            df = df.dropna(subset=['datetime'])
            if len(df) < initial_count:
                removed_count = initial_count - len(df)
                logger.warning(f"已移除 {removed_count} 个无效日期行")

            # 检查是否有有效数据
            if len(df) == 0:
                logger.error("数据加载后无有效记录")
                raise ValueError("数据加载后无有效记录")

            # 设置日期索引
            df.set_index('datetime', inplace=True)
            logger.info(f"日期范围: {df.index.min()} 至 {df.index.max()}, 共 {len(df)} 条记录")

        except Exception as e:
            logger.error(f"日期转换失败: {str(e)}")
            raise

        # 定义所有可能列
        all_columns = ['close', 'open', 'high', 'low', 'volume']

        # 处理数值列
        processed_columns = []
        for col in all_columns:
            if col in df.columns:
                # 记录处理前的样本数据
                sample_before = df[col].head(3).to_list()
                logger.debug(f"开始处理列 '{col}'，前3个值: {sample_before}")

                try:
                    # 处理特殊字符和格式
                    df[col] = df[col].astype(str).apply(cls._clean_numeric_value)

                    # 转换为浮点数
                    df[col] = pd.to_numeric(df[col], errors='coerce')

                    # 记录处理后的样本数据
                    sample_after = df[col].head(3).to_list()
                    logger.debug(f"列 '{col}' 处理后，前3个值: {sample_after}")

                    processed_columns.append(col)

                except Exception as e:
                    logger.error(f"处理列 '{col}' 时出错: {str(e)}")
                    logger.error(f"问题数据样本: {df[col].head(5).to_list()}")
                    raise

        # 确保列顺序一致
        df = df[processed_columns]
        logger.info(f"最终数据列顺序: {df.columns.tolist()}")

        # 检查空值
        for col in required_columns:
            if col in df.columns and df[col].isnull().any():
                null_count = df[col].isnull().sum()
                logger.warning(f"列 '{col}' 中有 {null_count} 个空值")

                # 记录包含空值的前5行
                null_rows = df[df[col].isnull()].head()
                logger.debug(f"包含空值的前5行:\n{null_rows}")

        # 排序索引
        df.sort_index(inplace=True)

        # 记录最终数据摘要
        logger.info(f"数据加载完成，形状: {df.shape}")
        logger.debug(f"处理后数据前5行:\n{df.head()}")

        return df

    @staticmethod
    def _clean_numeric_value(value):
        """
        清理数值字符串，移除逗号、货币符号等非数字字符
        并处理特殊后缀如M(百万)、B(十亿)，支持科学计数法

        参数:
            value: 原始值

        返回:
            str: 清理后的数值字符串
        """
        # 获取日志记录器
        logger = logging.getLogger(__name__)

        if pd.isna(value) or value is None:
            return ""

        try:
            # 转换为字符串并清理
            str_value = str(value).strip().upper()

            # 处理空字符串
            if not str_value or str_value == "NAN" or str_value == "N/A":
                return ""

            # 移除逗号、货币符号和其他非数字字符（保留小数点、负号、E）
            str_value = re.sub(r'[^\d\.\-E]', '', str_value)

            # 处理科学计数法 (优先级最高)
            if 'E' in str_value:
                try:
                    num = float(str_value)
                    return str(num)
                except ValueError:
                    # 尝试修复格式问题
                    parts = str_value.split('E')
                    if len(parts) == 2:
                        base = parts[0].replace('.', '').lstrip('0')
                        exponent = int(parts[1])
                        num = float(f"{base}E{exponent}")
                        return str(num)

            # 处理后缀 (M, B)
            if str_value.endswith('M'):
                base_val = str_value[:-1]
                num = float(base_val) * 1e6
                return str(num)

            if str_value.endswith('B'):
                base_val = str_value[:-1]
                num = float(base_val) * 1e9
                return str(num)

            return str_value

        except Exception as e:
            # 记录具体错误值
            logger.warning(f"数值清理失败: 原始值={value} 错误={str(e)}")
            return str(value)  # 保底返回原始字符串

    @classmethod
    def get_params(cls, config_file):
        """
        从配置文件获取数据列映射参数

        参数:
            config_file: 配置文件路径

        返回:
            dict: 列映射参数
        """
        logger = logging.getLogger(__name__)
        logger.info(f"加载配置文件: {config_file}")

        try:
            with open(config_file, 'r') as f:
                config = yaml.safe_load(f)
        except Exception as e:
            logger.error(f"加载配置文件失败: {str(e)}")
            raise

        # 获取列配置
        cols = config.get('columns', {})

        # 默认列映射
        default_mapping = {
            'dtformat': '%Y/%m/%d',
            'close': 1,  # 第1列
            'open': 2,  # 第2列
            'high': 3,  # 第3列
            'low': 4,  # 第4列
            'volume': 5,  # 第5列
            'openinterest': -1  # 默认不使用
        }

        # 合并配置（使用0-based索引）
        params = {}
        for key in default_mapping:
            # 配置文件中的索引转换为0-based
            config_value = cols.get(key, default_mapping[key])

            # 处理索引转换
            if isinstance(config_value, int):
                if config_value > 0:
                    params[key] = config_value - 1  # 转换为0-based索引
                else:
                    params[key] = config_value  # 负值表示不使用
            else:
                params[key] = config_value

        logger.info(f"获取到的列参数: {params}")
        return params